﻿using Heroius.Extension;
using Heroius.Extension.Box.Console;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;

namespace Heroius.Cert.OpenSSL
{
    /// <summary>
    /// 证书生成引擎
    /// </summary>
    public class Engine: IEngine
    {
        public void Init(Dictionary<string, string> setting)
        {
            Setting = OSetting = new OpenSSLSettings(setting);
        }

        public DictEvalEntity Setting { get; private set; }

        OpenSSLSettings OSetting;

        public GenResult Gen(CertInfo info)
        {
            try
            {
                CertConfig = info;
                
                ExecutionBox = new ConsoleBox(new FileInfo(OSetting.OpenSSLPath));
                ExecutionBox.OutputDataReceived += ExecutionBox_OutputDataReceived;
                ExecutionBox.ExecutionCompleted += ExecutionBox_ExecutionCompleted;

                info.WorkingDirectory = info.WorkingDirectory.Replace('\\', '/'); //openssl 不识别反斜杠

                if (!GenKey()) return new GenResult() { Info = "Timeout" };
                if (!GenRequest()) return new GenResult() { Info = "Timeout" };
                GenCert(info);
                return GenResult.SucceedResult;
            }
            catch(Exception ex)
            {
                return new GenResult() { Info = ex.Message };
            }
        }

        public event Action<string> SendMessage;

        /// <summary>
        /// 一次执行的证书配置
        /// </summary>
        CertInfo CertConfig;

        /// <summary>
        /// 执行盒子
        /// </summary>
        ConsoleBox ExecutionBox { get; set; }

        #region Proc Actions

        /// <summary>
        /// 证书生成进度状态
        /// </summary>
        string GenState { get; set; }

        /// <summary>
        /// 生成密钥
        /// </summary>
        bool GenKey()
        {
            GenState = "key";
            var Arguments = $"genrsa {(string.IsNullOrEmpty(CertConfig.KeyPassphrase) ? "" : "-aes256 ")}-out {CertConfig.CertId}.key {CertConfig.KeyBits}";
            ResetExecutionStatement();
            ExecutionBox.Start(Arguments, CertConfig.WorkingDirectory);
            if (!string.IsNullOrEmpty(CertConfig.KeyPassphrase))
            {
                ExecutionBox.InputLine(CertConfig.KeyPassphrase);//todo: 此种方式无法有效输入
                ExecutionBox.InputLine(CertConfig.KeyPassphrase);
            }
            return WaitUntilExit();
        }
        /// <summary>
        /// 生成签发请求
        /// </summary>
        bool GenRequest()
        {
            GenState = "req";
            bool selfsign = string.IsNullOrEmpty(CertConfig.IssuerId);
            if (selfsign) return true;
            else
            {
                var Arguments = $"req -new -sha256 -key {CertConfig.CertId}.key -out {CertConfig.CertId}.csr";
                ResetExecutionStatement();
                ExecutionBox.Start(Arguments, CertConfig.WorkingDirectory);
                if (!string.IsNullOrEmpty(CertConfig.KeyPassphrase))
                {
                    ExecutionBox.InputLine(CertConfig.KeyPassphrase);
                }
                InputFields();
                return WaitUntilExit();
            }
        }
        /// <summary>
        /// 生成证书
        /// </summary>
        /// <param name="cert"></param>
        void GenCert(CertInfo cert)
        {
            GenState = "gen";
            bool selfsign = string.IsNullOrWhiteSpace(cert.IssuerId);
            if (selfsign)
            {
                var Arguments = $"req -sha256 -new -x509 -days {cert.Days} -key {cert.CertId}.key -out {cert.CertId}.crt";
                ResetExecutionStatement();
                ExecutionBox.Start(Arguments, cert.WorkingDirectory);
                if (!string.IsNullOrWhiteSpace(cert.KeyPassphrase))
                {
                    ExecutionBox.InputLine(cert.KeyPassphrase);
                }
                InputFields();
                WaitUntilExit();
            }
            else
            {
                CreateNewConfigFile();
                var Arguments = $"ca -batch -config {cert.CertId}.conf -notext -in {cert.CertId}.csr -out {cert.CertId}.crt";
                ResetExecutionStatement();
                ExecutionBox.Start(Arguments, cert.WorkingDirectory);
                WaitUntilExit();
            }

            if (!string.IsNullOrWhiteSpace(cert.PfxExportPassword))
            {
                var Arguments = $"pkcs12 -export -in {cert.CertId}.crt -inkey {cert.CertId}.key -out {cert.CertId}.pfx";
                ExeboxFinishedMark = false;
                ExecutionBox.Start(Arguments, cert.WorkingDirectory);
                ExecutionBox.InputLine(cert.PfxExportPassword);//todo: 此种方式无法有效输入
                ExecutionBox.InputLine(cert.PfxExportPassword);//verify
                WaitUntilExit();
            }
        }

        /// <summary>
        /// 向标准输入流中依次输入证书字段
        /// </summary>
        void InputFields()
        {
            TryInputReqField(CertConfig.CountryName);
            TryInputReqField(CertConfig.StateName);
            TryInputReqField(CertConfig.CityName);
            TryInputReqField(CertConfig.OrgName);
            TryInputReqField(CertConfig.OrgUnitName);
            TryInputReqField(CertConfig.CommonName);
            TryInputReqField(CertConfig.Email);
            TryInputReqField(CertConfig.ChallengePassword);
            TryInputReqField(CertConfig.OptionalCoName);
        }
        /// <summary>
        /// 尝试填写证书请求中的信息字段，当字段值为空时，自动填写“.”
        /// </summary>
        /// <param name="field"></param>
        void TryInputReqField(string field)
        {
            if (string.IsNullOrWhiteSpace(field))
            {
                ExecutionBox.InputLine(".");
            }
            else
            {
                ExecutionBox.InputLine(field);
            }
        }
        /// <summary>
        /// 创建为当前生成证书准备的签名配置文件
        /// </summary>
        void CreateNewConfigFile()
        {
            bool selfsign = string.IsNullOrEmpty(CertConfig.IssuerId);

            //为签发者尝试建立签署数据库
            string idxfilename = $"{CertConfig.WorkingDirectory}/{CertConfig.IssuerId}.index";
            if (!File.Exists(idxfilename))
                File.Create(idxfilename).Close();

            var serialfilename = $"{CertConfig.WorkingDirectory}/{CertConfig.IssuerId}.serial";
            if (!File.Exists(serialfilename))
            {
                var serial = new StreamWriter(File.Create(serialfilename));
                serial.Write("1000");
                serial.Close();
            }
            var cnfilename = $"{CertConfig.WorkingDirectory}/{CertConfig.IssuerId}.crlnumber";
            if (!File.Exists(cnfilename))
            {
                var crlnum = new StreamWriter(File.Create(cnfilename));
                crlnum.Write("1000");
                crlnum.Close();
            }

            //为当前证书创建签发配置
            StreamWriter confwriter = new StreamWriter($"{CertConfig.WorkingDirectory}/{CertConfig.CertId}.conf", false);

            confwriter.WriteLine("[ca]");
            confwriter.WriteLine("default_ca=myca");

            confwriter.WriteLine("[crl_ext]");
            confwriter.WriteLine("issuerAltName = issuer:copy");
            confwriter.WriteLine("authorityKeyIdentifier = keyid:always");

            confwriter.WriteLine("[myca]");
            confwriter.WriteLine($"dir = \"{CertConfig.WorkingDirectory}\"");//不可在路径末尾添加斜杠！
            confwriter.WriteLine($"new_certs_dir = \"{CertConfig.WorkingDirectory}\"");//不可在路径末尾添加斜杠！
            confwriter.WriteLine("unique_subject = no");
            confwriter.WriteLine($"certificate = \"{CertConfig.WorkingDirectory}/{CertConfig.IssuerId}.crt\"");
            confwriter.WriteLine($"database = \"{idxfilename}\"");
            confwriter.WriteLine($"private_key = \"{CertConfig.WorkingDirectory}/{CertConfig.IssuerId}.key\"");
            confwriter.WriteLine($"serial = \"{serialfilename}\"");
            confwriter.WriteLine($"default_days = {CertConfig.Days}");
            confwriter.WriteLine("default_md = sha1");
            confwriter.WriteLine("policy = myca_policy");
            confwriter.WriteLine("x509_extensions = myca_extensions");
            confwriter.WriteLine($"crlnumber = \"{cnfilename}\"");
            confwriter.WriteLine($"default_crl_days = {CertConfig.Days}");

            confwriter.WriteLine("[myca_policy]");
            confwriter.WriteLine("commonName = supplied");
            confwriter.WriteLine("stateOrProvinceName = supplied");
            confwriter.WriteLine("countryName = optional");
            confwriter.WriteLine("emailAddress = optional");
            confwriter.WriteLine("organizationName = supplied");
            confwriter.WriteLine("organizationalUnitName = optional");

            confwriter.WriteLine("[myca_extensions]");
            confwriter.WriteLine($"basicConstraints = critical,CA:{CertConfig.IsCA.ToString().ToUpper()}");
            confwriter.WriteLine("keyUsage = critical, any");//??
            confwriter.WriteLine("subjectKeyIdentifier = hash");
            confwriter.WriteLine("authorityKeyIdentifier = keyid:always,issuer");
            if (CertConfig.KeyUsages.Count > 0)
            {
                confwriter.WriteLine($"keyUsage = {CertConfig.KeyUsages.Select(ku=>ku.ToProperString()).Merge(",")}");
            }
            if (CertConfig.EnhancedKeyUsages.Count > 0)
            {
                confwriter.WriteLine($"extendedKeyUsage = {CertConfig.EnhancedKeyUsages.Select(eku => eku.ToProperString()).Merge(",")}");
            }
            if (CertConfig.Crls.Count > 0) confwriter.WriteLine("crlDistributionPoints = @crl_section");
            if (CertConfig.AlterNames.Count > 0) confwriter.WriteLine("subjectAltName  = @alt_names");
            if (CertConfig.OCSP.Count > 0) confwriter.WriteLine("authorityInfoAccess = @ocsp_section");

            confwriter.WriteLine("[v3_ca]");
            confwriter.WriteLine($"basicConstraints = critical,CA:{CertConfig.IsCA.ToString().ToUpper()}");
            confwriter.WriteLine("keyUsage = critical,any");//??
            confwriter.WriteLine("subjectKeyIdentifier = hash");
            confwriter.WriteLine("authorityKeyIdentifier = keyid:always,issuer");
            if (CertConfig.KeyUsages.Count > 0)
            {
                confwriter.WriteLine($"keyUsage = {CertConfig.KeyUsages.Select(ku => ku.ToProperString()).Merge(", ")}");
            }
            if (CertConfig.EnhancedKeyUsages.Count > 0)
            {
                confwriter.WriteLine($"extendedKeyUsage = {CertConfig.EnhancedKeyUsages.Select(eku => eku.ToProperString()).Merge(",")}");
            }
            if (CertConfig.Crls.Count > 0) confwriter.WriteLine("crlDistributionPoints = @crl_section");
            if (CertConfig.AlterNames.Count > 0) confwriter.WriteLine("subjectAltName  = @alt_names");
            if (CertConfig.OCSP.Count > 0) confwriter.WriteLine("authorityInfoAccess = @ocsp_section");

            if (CertConfig.AlterNames.Count > 0)
            {
                confwriter.WriteLine("[alt_names]");
                for (int i = 0; i < CertConfig.AlterNames.Count; i++)
                {
                    confwriter.WriteLine($"DNS.{i} = {CertConfig.AlterNames[i]}");
                }
            }
            if (CertConfig.Crls.Count > 0)
            {
                confwriter.WriteLine("[crl_section]");
                for (int i = 0; i < CertConfig.Crls.Count; i++)
                {
                    confwriter.WriteLine($"URI.{i} = {CertConfig.Crls[i]}");
                }
            }
            if (CertConfig.OCSP.Count > 0)
            {
                confwriter.WriteLine("[ocsp_section]");
                for (int i = 0; i < CertConfig.OCSP.Count; i++)
                {
                    confwriter.WriteLine($"caIssuer;URI.{i} = {CertConfig.OCSP[i].IssuerCert}");
                    confwriter.WriteLine($"OCSP;URI.{i} = {CertConfig.OCSP[i].OCSP}");
                }
            }
            confwriter.Close();
        }

        #endregion

        #region Execution Statements

        /// <summary>
        /// 运行时变量标记：当前OpenSSL调用是否已经执行完成
        /// </summary>
        bool ExeboxFinishedMark = true;
        /// <summary>
        /// 当次执行已经的等待的时间累加，ms
        /// </summary>
        int ExeboxWaitingSpan = 0;

        /// <summary>
        /// 重置进程执行状态到开始执行的状态
        /// </summary>
        void ResetExecutionStatement()
        {
            ExeboxFinishedMark = false;
            ExeboxWaitingSpan = 0;
        }

        /// <summary>
        /// 等待当前执行完成后返回
        /// </summary>
        /// <returns>成功执行还是超时终止</returns>
        bool WaitUntilExit()
        {
            while (!ExeboxFinishedMark)
            {
                Thread.Sleep(OSetting.ProcWaitInterval);
                ExeboxWaitingSpan += OSetting.ProcWaitInterval;
                if (ExeboxWaitingSpan > OSetting.ProcWaitLimit)
                {
                    Stop();
                }
            }
            return true;
        }

        /// <summary>
        /// 终止
        /// </summary>
        public void Stop()
        {
            if (!ExeboxFinishedMark) ExecutionBox.Terminate(); //terminate 也会引发终止事件，自行修改FinishedMark状态
        }

        #endregion

        #region Stream IO Events

        private void ExecutionBox_ExecutionCompleted(object sender, ExecutionCompletedArgs e)
        {
            ExeboxFinishedMark = true;
            string info;
            if (e.FeedbackValue != null)
            {
                info = e.FeedbackValue.ToString();
            }
            else
            {
                info = e.ExitStatus.ToString();
            }
            SendMessage?.Invoke(info);
        }

        private void ExecutionBox_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            //why no data？OpenSSL采用了主程序调用子程序的方式执行，在执行中不从标准输出流中输出数据，而仅在模块调用完成时返回反馈消息
            SendMessage?.Invoke(e.Data);
        }

        #endregion

    }
}
